Skip to content

build: add --shared-v8 flags#62097

Closed
jblac wants to merge 1 commit intonodejs:mainfrom
epochful:shared-v8
Closed

build: add --shared-v8 flags#62097
jblac wants to merge 1 commit intonodejs:mainfrom
epochful:shared-v8

Conversation

@jblac
Copy link

@jblac jblac commented Mar 4, 2026

Summary

Add --shared-v8 support using the configure_library pattern, matching
every other shared dependency in Node.js. Includes configure-time
validation of the shared V8's build configuration and documentation of
the V8 Build Configuration Spec in BUILDING.md and doc/api/cli.md.

What this does

  • Adds --shared-v8, --shared-v8-includes, --shared-v8-libpath,
    --shared-v8-libname flags
  • Calls configure_library('v8', o) inside configure_v8() (same
    pattern as OpenSSL, SQLite, LIEF)
  • Guards all deps/v8/include references, abseil dependencies,
    deps/v8/tools/*.mjs deps_files, and deps/v8/include/v8.h
    sources behind node_shared_v8=="false"
  • Adds rpath for node_mksnapshot to find shared V8 at build time
  • Deprecates --without-bundled-v8 (aliased to --shared-v8)
  • Validates the shared V8 at configure time (see below)
  • Documents the V8 Build Configuration Spec in BUILDING.md

What this does NOT do

  • Does not change the default build (bundled V8 is still the default)
  • Does not disable snapshot generation (snapshots work correctly with
    shared V8; node_mksnapshot links against the configured V8)

The floating patches concern: resolved

Node.js floats ~12 patches over bundled V8. Decomposition shows these
are NOT source-level API changes. They are build configuration flags
and compiler compatibility fixes:

Build flags (hard errors at configure time):

  • v8_enable_sandbox=false - required because Node.js backing
    stores live in C++ memory. Standard V8 build flag.
  • v8_enable_extensible_ro_snapshot=false - required for snapshot
    compatibility. Standard V8 build flag.
  • v8_enable_pointer_compression - must match between Node.js and
    V8 or struct layouts differ. Auto-detected from v8config.h.

Build flags (warning at configure time):

  • v8_promise_internal_field_count=1 - recommended for fast
    async_hooks Promise tracking. When 0, a slower symbol-property
    fallback is used. Standard V8 build flag, not a source patch.

Compiler fixes (irrelevant for shared V8):

  • MSVC inline method fixes, warning suppression. These affect V8
    compilation, not Node.js compilation. A pre-built shared V8 has
    already dealt with its own compiler issues.

Standard V8 embedder API (not patched):

  • Promise hooks, context embedder data, snapshot creator. All
    upstream V8 API that Node.js uses but does not modify.

The configure step hard-errors if the shared V8 doesn't meet
requirements, with clear messages explaining exactly which V8 build
flag to change. Distributors get a concrete, machine-verifiable spec
instead of "good luck with the floating patches."

V8 build flags for distributors

Hard requirements (configure errors):

v8_enable_sandbox = false               # C++ backing stores incompatible
v8_enable_extensible_ro_snapshot = false # Snapshot compatibility
v8_enable_pointer_compression = <match> # Auto-detected from shared V8
V8 major.minor version must match       # e.g., 14.3.x

Performance (configure warns):

v8_promise_internal_field_count = 1     # Fast async_hooks (fallback exists)

Testing

The test-shared.yml workflow exercises --shared-v8 on every PR.
tools/nix/v8.nix builds V8 separately from deps/v8/ as static
libraries with a pkg-config file, and shell.nix passes --shared-v8
(updated from the deprecated --without-bundled-v8). The full test
suite runs against the shared V8 build on x86_64-linux, aarch64-linux,
x86_64-darwin, and aarch64-darwin.

Test What it validates
./configure --shared-v8 with pkg-config configure_library pkg-config path works
./configure --shared-v8 --shared-v8-includes=... --shared-v8-libpath=... --shared-v8-libname=v8_monolith Manual path overrides work
./configure --without-bundled-v8 Deprecated flag prints warning, aliases to --shared-v8
./configure --shared-v8 --enable-d8 Fails with clear error
./configure --shared-v8 --enable-v8windbg Fails with clear error
Version mismatch: point at V8 13.x headers Hard error from validate_shared_v8()
Promise field count = 0 in shared headers Warning about slower async_hooks fallback
Pointer compression mismatch Auto-detected and matched from shared V8
V8 sandbox enabled in shared build Hard error: "rebuild V8 with v8_enable_sandbox=false"
Extensible RO snapshot enabled Hard error: "rebuild V8 with v8_enable_extensible_ro_snapshot=false"
node -e "console.log(process.versions.v8)" Reports correct shared V8 version
node -e "console.log('hello')" Basic functionality with shared V8
node -p "process.config.variables.node_shared_v8" Reports true
Full test suite (make test) No regressions
Snapshot validation: node --v8-pool-size=0 -e "1+1" Snapshot loaded correctly from shared V8 build

Build validation

Verify that when --shared-v8 is used, nothing from deps/v8/ is compiled:

find out/Release/obj/deps/v8/ -name '*.o' 2>/dev/null | wc -l       # Expected: 0
find out/Release/obj/tools/v8_gypfiles/ -name '*.o' 2>/dev/null | wc -l  # Expected: 0

Fixes: #53509

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/build
  • @nodejs/gyp
  • @nodejs/tsc

@nodejs-github-bot nodejs-github-bot added build Issues and PRs related to build files or the CI. doc Issues and PRs related to the documentations. needs-ci PRs that need a full CI run. labels Mar 4, 2026
doc/api/cli.md Outdated
Comment on lines +3287 to +3312
### `--shared-v8`

<!-- YAML
added: REPLACEME
-->

> Build-time `configure` flag.

Link to a shared V8 library instead of building the bundled copy from
`deps/v8/`. When used, the bundled V8 is completely excluded from
compilation.

Related configure flags:

* `--shared-v8-includes=<path>`: Directory containing V8 header files.
* `--shared-v8-libpath=<path>`: Directory containing the shared V8 library.
* `--shared-v8-libname=<name>`: Library name(s) to link against,
comma-separated. Default: `v8,v8_libplatform`.

The shared V8 must be ABI-compatible with the bundled V8 version.
Configure-time validation enforces version match, sandbox disabled, and
pointer compression parity. See [Building to use shared dependencies at
runtime][] in `BUILDING.md` for full requirements.

The deprecated `--without-bundled-v8` flag is aliased to `--shared-v8`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove, this page documents the runtime flags for the node executable, not the ./configure.py ones

@aduh95
Copy link
Contributor

aduh95 commented Mar 4, 2026

If you wanna prove this works, we would need to remove deps/v8 from the slim tarball and have test-shared being able to build V8 from source somehow.
In case that helps, I have a branch doing that for V8 own deps: main...aduh95:node:shared-abseil

Add first-class --shared-v8 support using the same configure_library()
pattern used by every other shared dependency in Node.js. This provides
--shared-v8-includes, --shared-v8-libpath, and --shared-v8-libname
flags with pkg-config fallback.

When --shared-v8 is used, the bundled deps/v8/ is completely excluded
from compilation. No v8 GYP targets are built, no bundled headers
are referenced. The external v8 is linked via the standard shared
mechanism.

Configure-time validation checks the shared v8 against Node.js
requirements. Hard errors on: version mismatch, v8 sandbox enabled,
extensible RO snapshot enabled, pointer compression ABI mismatch
(auto detected). warning on: V8_PROMISE_INTERNAL_FIELD_COUNT < 1
(async_hooks uses a slower fallback). These are the "floating patch"
requirements decomposed into verifiable build flags, not source patches.

Snapshot generation (node_mksnapshot) works correctly with shared v8
because it links against the Node.js library, which transitively links
against whatever v8 is configured. No snapshot disabling needed.

The existing --without-bundled-v8 flag is deprecated and aliased
to --shared-v8 for backwards compatibility.

Fixes: nodejs#53509
@jblac
Copy link
Author

jblac commented Mar 4, 2026

If you wanna prove this works, we would need to remove deps/v8 from the slim tarball and have test-shared being able to build V8 from source somehow. In case that helps, I have a branch doing that for V8 own deps: main...aduh95:node:shared-abseil

@aduh95
test-shared already builds v8 separately via v8.nix and passes in the --shared-v8 flag. Are you looking for something more specific than this?

@aduh95
Copy link
Contributor

aduh95 commented Mar 4, 2026

So v8.nix does build a version of v8, but that's the Node.js own vendored copy, and Node.js own build system. That's at-odd with the other shared libraries that are being tested that get build from upstream source, and the test-shared workflow does not even have the vendored version available, see

node/Makefile

Lines 1216 to 1238 in 330e3ee

ifeq ($(SKIP_SHARED_DEPS), 1)
$(RM) -r $(TARNAME)/deps/ada
$(RM) -r $(TARNAME)/deps/brotli
$(RM) -r $(TARNAME)/deps/cares
$(RM) -r $(TARNAME)/deps/crates
$(RM) -r $(TARNAME)/deps/googletest
$(RM) -r $(TARNAME)/deps/histogram
$(RM) -r $(TARNAME)/deps/icu-small
$(RM) -r $(TARNAME)/deps/icu-tmp
$(RM) -r $(TARNAME)/deps/LIEF
$(RM) -r $(TARNAME)/deps/llhttp
$(RM) -r $(TARNAME)/deps/merve
$(RM) -r $(TARNAME)/deps/nbytes
$(RM) -r $(TARNAME)/deps/nghttp2
$(RM) -r $(TARNAME)/deps/ngtcp2
find $(TARNAME)/deps/openssl -maxdepth 1 -type f ! -name 'nodejs-openssl.cnf' -exec $(RM) {} +
find $(TARNAME)/deps/openssl -mindepth 1 -maxdepth 1 -type d -exec $(RM) -r {} +
$(RM) -r $(TARNAME)/deps/simdjson
$(RM) -r $(TARNAME)/deps/sqlite
$(RM) -r $(TARNAME)/deps/uv
$(RM) -r $(TARNAME)/deps/uvwasi
$(RM) -r $(TARNAME)/deps/zlib
$(RM) -r $(TARNAME)/deps/zstd

What I'd be looking for is some PoC that one can build Node.js without deps/v8 (or at least, with only a very small subset of deps/v8), i.e. building V8 from the upstream source, using the upstream build system. I don't think it makes sense to introduce a --shared-v8 configure flag if it's not actually possible to build Node.js without using the vendored version.

@jblac
Copy link
Author

jblac commented Mar 4, 2026

The other shared deps in that Makefile (zlib, openssl, etc.) don't require Node.js to prove they can be built from upstream source using the upstream build system either. Distributors handle that. --shared-openssl doesn't ship an OpenSSL build script. It provides the linking flags and expects a compatible library at the path you give it.

That said, I can fix two things:

  1. add deps/v8 to the SKIP_SHARED_DEPS removal list
  2. make the version check handle the absence of bundled headers gracefully (skip the comparison if deps/v8/include/v8-version.h doesn't exist).

Would that address your concern?

@aduh95
Copy link
Contributor

aduh95 commented Mar 4, 2026

The other shared deps in that Makefile (zlib, openssl, etc.) don't require Node.js to prove they can be built from upstream source using the upstream build system either.

I don't know if they require it, but the test-shared does validate exactly that: tools/nix/sharedLibDeps.nix contains all the info to get their source, build it, and integrate the resulting shared objects with pkgconfig into the final build.

  1. add deps/v8 to the SKIP_SHARED_DEPS removal list

Yeah that's exactly what I'm asking. But note that v8.nix will stop working as it expects to find a deps/v8 folder at the root of the repo.

2. make the version check handle the absence of bundled headers gracefully (skip the comparison if deps/v8/include/v8-version.h doesn't exist).

That seems a good improvement, yes. Another possiblity would be to remove the whole deps/v8 folder but the file we need for the comparison, like we do for openssl. I for one would be fine not having those checks at all tbh.

@jblac
Copy link
Author

jblac commented Mar 4, 2026

Alright, that makes much more sense. I do think the rewrite of v8.nix to build from upstream V8 source would be better served in a follow-up PR though. Adding deps/v8 to SKIP_SHARED_DEPS and rewriting v8.nix are coupled since the current derivation sources from deps/v8/, so those should land together. The follow-up would keep the necessary headers (like v8-version.h, v8config.h, v8-promise.h) the same way openssl keeps nodejs-openssl.cnf, so the validation checks in this PR continue to work. The scope of this PR is the --shared-v8 configure plumbing: the flags, GYP guards, validation, and documentation. The current v8.nix and test-shared already validate that the configure changes work correctly, which is the point of this PR. How V8 gets built and packaged as a shared library is orthogonal to the linking infrastructure this PR provides, and the configure flags work identically regardless of whether the shared V8 was built from vendored or upstream source.

@aduh95
Copy link
Contributor

aduh95 commented Mar 4, 2026

Yeah two PRs would make more sense, but this one would be blocked on the other one. BTW, if Nix is not your cup of tea, it's not at all a hard requirement; as long as you're able to show me a PoC where you:

  1. build V8 from source
  2. get the Node.js source, removing all files from deps/v8 (except the strictly necessary ones)
  3. pass all the tests

Can be a dockerfile, a GHA workflow file, or whatever else.

The scope of this PR is the --shared-v8 configure plumbing

I get that, but my point is that I'm doubtful there's a use-case for it unless one can actually build V8 independently of Node.js build system.

@jblac
Copy link
Author

jblac commented Mar 4, 2026

These two concerns are independent. This PR adds configure plumbing that lets Node.js link against any external V8: the flags, GYP guards, validation, and documentation. The follow-up changes where CI sources its V8 from. The configure flags work identically regardless of whether the shared V8 was built from vendored source, upstream source, or an alternative engine with a V8 API shim.

Every distributor who would use --shared-v8 already has their own V8 build pipeline. Fedora, Debian, Gentoo, NixOS - they're not waiting for Node.js to teach them how to build V8. They're waiting for Node.js to give them a supported flag to link against the V8 they already have. That's what this PR provides.

Requiring a proof of concept that builds V8 from upstream source is asking this PR to solve a problem it doesn't need to solve.

I want to be straightforward: for my own use case, I don't need this merged. I'm offering it upstream because it solves a real problem for distributors and closes #53509. If the requirement is that I also rewrite the CI infrastructure to build V8 from upstream before this can land, I'll respectfully close the PR and move on.

@aduh95
Copy link
Contributor

aduh95 commented Mar 4, 2026

Every distributor who would use --shared-v8 already has their own V8 build pipeline. Fedora, Debian, Gentoo, NixOS

Do you have some ref for that? I'm maintaining Node.js packages on nixpkgs for NixOS, if we already have a V8 build there that's awesome, don't know how I missed that!

The configure flags work identically regardless of whether the shared V8 was built from vendored source, upstream source, or an alternative engine with a V8 API shim.

How do we know that without testing it? That feels extremely optimistic

@jblac jblac closed this Mar 4, 2026
@jblac jblac deleted the shared-v8 branch March 4, 2026 14:49
@jblac
Copy link
Author

jblac commented Mar 4, 2026

For reference, since you mentioned maintaining Node.js packages on NixOS: the V8 JavaScript Engine (v8 in nixpkgs) is available under development/libraries/v8 in the nixpkgs tree. Distributors have been building V8 separately for years. That's the entire motivation behind #53509.

The configure plumbing in this PR works regardless of where the shared V8 comes from. That's not optimism, that's how shared library linking works. The flags pass paths to the linker. The linker doesn't care about provenance.

As I said, I don't need this merged. I offered it upstream to help distributors and anyone who wants to use an alternative engine. The code is available at epochful/node for anyone who wants it.

@aduh95
Copy link
Contributor

aduh95 commented Mar 4, 2026

The configure plumbing in this PR works regardless of where the shared V8 comes from. That's not optimism, that's how shared library linking works. The flags pass paths to the linker. The linker doesn't care about provenance.

I should clarify what I call "optimism" is not using shared libs, it's introducing untested changes, and claiming it works. There are a bunch of unknown, on the top of my head:

  • does the GYP changes correctly removes all the vendored deps/v8 files from the build recipe?
  • is it actually possible to build a shared lib from the upstream source that includes all the required symbols?
  • what about V8 deps (libhwy, v8_zlib, absl)? Who is going to supply those to the linker?
  • is the project able to maintain those flags if we have no process to validate changes?

Having a PoC would resolve all those concerns.

the V8 JavaScript Engine (v8 in nixpkgs) is available under development/libraries/v8 in the nixpkgs tree

That doesn't seem to be the case, at least not on the master branch.

$ ls pkgs/development/libraries/v8
ls: pkgs/development/libraries/v8: No such file or directory
$ find . -name v8
$ git rev-parse HEAD
d72fb2f9b18860f71e26cdf1628357beecddec7e

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Issues and PRs related to build files or the CI. doc Issues and PRs related to the documentations. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Option to have a shared V8 library?

3 participants